
/*-------------------------------------------------------*/
/* I N T E R F A C E                                     */
/*                                                       */
/* [c]copyright 1995 by AlphaHelix                       */
/*                                                       */
/*-------------------------------------------------------*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "diskio.hpp"
#include "error.hpp"
#include "memory.hpp"
#include "xlib.hpp"
#include "input.hpp"
#include "sound.hpp"
#include "setup.hpp"


/*---------------------------------------------------------
 Function:

 Description:
 Custom MENU definitions.
---------------------------------------------------------*/
// Prototypes of custom menu functions.
int a_main(int, int);
// -

enum Control { END, HEAD, TEXT, STEXT, ADD, ADDSUB, YES, YESNO, UPDOWN, CUSTOM };

struct Menu {
   Control  type;                // Control Type.
   char     *sname;              // File name of sprites.
   Sprite   *spr;                // Set to 0.
   int      x, y;                // Position.
   int      ys;                  // ?
   char     *text;               // Text to display.
   Back     *back;               // Placeholder (set to 0)
   int      (*action)(int, int);
   int      fast;
};

/*
 The meaning of the parameters passed to the action function.
 index    : Which entry in the menu structure gets message.
 subindex : Which subpart of entry "index".
 Special values:
 index == M_INIT: Initialize menu.
 index == M_KEY: Key pressed message.
 index == M_JOYPAD : JOYPAD message.
*/

static Menu mainmenu[] = {
   { ADD, "",0,0,50,0,"",0,a_main,0 },
   { ADDSUB, "",0,0,70,0,"",0,a_main,1 },
   { ADD,"",0,0,90,0,"",0,a_main,0 },
   { STEXT,"",0,0,8,0,"",0,0,0 },
   { STEXT,"",0,0,18,0,"",0,0,0 },
   { YES, "", 0, 0, 120, 0, "Save and Exit",0,a_main,0 },
   { END, "",0,0,0,0,"",0,a_main,0 }
};


/*---------------------------------------------------------
 Function Group: Menu Functions

 Description:
 These are functions to build and manage the various MENUs.
 A menu is made of an array of Menu structures.
 Each entry in the array defines another button, text or
 whatsoever. A custom function is linked to all the buttons
 which is being executed on a button PRESS event.
---------------------------------------------------------*/
#define MENUX           40
#define MENUY           80
#define MENUXS          200
#define MENUYS          150
#define MENUX1          (MENUX+MENUXS-1)
#define MENUY1          (MENUY+MENUYS-1)

// Events.
#define M_INIT          99       // Initialize menu.
#define M_KEY           98       // Key has been pressed.
#define M_JOYPAD        97       // Joystick event.

static Back    *menuback;        // Menu screen back ground.
static Sprite  *pointer;         // Mouse pointer.
static Back    *back;            // Pointer background.
static Font    *blue;            // Big Blue font.
static Font    *tiny;            // Tiny font. Used everywhere.
static Sprite  *addsub;          // Add/Sub buttons.
static Sprite  *yesno;           // Yes/No buttons.
static Menu    *nowmenu;         // Which menu is active now.

// Backoff menu. This menu will be built, if user presses ESC.
static Menu    *backoff;
static int     pointer_enabled;  // RW: 1 if menu cursor is disabled.
static int     keys_enabled;     // RW: 1 if key events are welcome.
static int     joy_enabled;      // RW: 1 if joypad event are enabled.


//-------------------------------------------------------------------
// Button stuff.
//-------------------------------------------------------------------
// Button definition structure.
class Button {
   int      x, y;       // x,y of button.
   int      xs, ys;
   Sprite   *spr;       // Sprite representing this button.
   int      n;          // Sprite frame used to display released button.
   int      index;      // Return this value if button was pressed.
   Button   *next;
public:
   Button(int dx, int dy, Sprite *sprite, int dn, int idx);
   void     up(void) { x_drawsprite(spr, x, y, n); }
   void     down(void) { x_drawsprite(spr, x, y, n + 1); }
   friend   Button *checkbuttons(int x0, int y0, int &index, int &subindex);
   friend   void destroybuttons(void);
};
static Button  *anchor = 0;

Button::Button(int dx, int dy, Sprite *sprite, int dn, int idx)
{
   x = dx; y = dy;
   spr = sprite;
   xs = spr->xs; ys = spr->ys;
   n = dn*2;
   index = idx;
   if (anchor) next = anchor; else next = 0;
   anchor = this;
   up();
}


static void destroybuttons(void)
{
   Button   *b = anchor;
   Button   *keep;

   while (anchor) {
      keep = anchor->next;
      delete anchor;
      anchor = keep;
   }
}

// Check mouse coordinates and return number of button mouse hits.
static Button *checkbuttons(int x0, int y0, int &index, int &subindex)
{
   Button   *b = anchor;
   int      dx, dy;

   if (!b) {
      index = -1;
      return 0;
   }

   do {
      dx = x0 - b->x; dy = y0 - b->y;
      if ((dx >= 0) && (dy >= 0)) {
         if ((dx < b->xs) && (dy < b->ys)) {
            index = b->index; subindex = b->n / 2;
            return b;
         }
      }
   } while (b = b->next);
   index = -1;
   return 0;
}


/*---------------------------------------------------------
 Function group: MENUS

 Description:
 Build the MENU screen.
 Play game demo & ask player: What's next ?
---------------------------------------------------------*/
static void addtext1(Menu &menu, char *text1, char *text2)
{
   int   x, y;

   menu.back->restore();
   x = MENUX + 2;
   y = MENUY + menu.y + (addsub->ys-tiny->height()) / 2;
   tiny->vanilla(x, y, text1, 15);
   x += tiny->textlen(text1);
   tiny->vanilla(x, y, text2, 12);
}

static void destroymenu(void)
{
   if (!nowmenu) return;
   destroybuttons();
   while (nowmenu->type != END) {
      if (nowmenu->spr) unloadfile(nowmenu->spr);
      if (nowmenu->back) delete nowmenu->back;
      nowmenu++;
   }
   menuback->restore();
}

static void buildmenu(Menu *menu)
{
   int      index;
   int      x, y;
   int      i, n;

   destroymenu();
   nowmenu = menu;

// Set event handling to default.
   pointer_enabled = 1;
   keys_enabled = 0;
   joy_enabled = 0;

   index = 0;
   do {
      switch (menu->type) {
      case HEAD:
         blue->print_c(MENUX+(MENUXS+BORDER)/2, MENUY+menu->y, menu->text);
         break;
      case TEXT:
         if (menu->x == -1)
            tiny->vanilla_c(MENUX+(MENUXS+BORDER)/2, MENUY + menu->y, menu->text, menu->ys);
         else
            tiny->vanilla(MENUX + menu->x, MENUY + menu->y, menu->text, menu->ys);
         break;
      case STEXT:
         y = tiny->height() + 1;
         menu->back = new Back(MENUX1-23-MENUX, y);
         menu->back->save(MENUX, MENUY+menu->y);
         break;
      case ADD:
         y = tiny->height()*2 + 1;
         menu->back = new Back(MENUX1-23-MENUX, y);
         y = (addsub->ys - y) / 2;
         new Button(MENUX1-21, MENUY+menu->y, addsub, 0, index);
         menu->back->save(MENUX, MENUY+menu->y+y-1);
         break;
      case ADDSUB:
         y = tiny->height()*2 + 1;
         menu->back = new Back(MENUX1-47-MENUX, y);
         y = (addsub->ys - y) / 2;
         new Button(MENUX1-45, MENUY+menu->y, addsub, 0, index);
         new Button(MENUX1-21, MENUY+menu->y, addsub, 1, index);
         menu->back->save(MENUX, MENUY+menu->y+y-1);
         break;
      case YESNO:
         blue->print_c(MENUX+(MENUXS+BORDER)/2, 180, menu->text);
         new Button(MENUX+20, 205, yesno, 0, index);
         new Button(XMAX-10-yesno->xs, 205, yesno, 1, index);
         break;
      case YES:
         new Button(MENUX1-yesno->xs, MENUY+menu->y, yesno, 0, index);
         tiny->vanilla(MENUX1-yesno->xs-tiny->textlen(menu->text)-10,
                       MENUY+menu->y+(yesno->ys-tiny->height())/2,
                       menu->text, 15);
         break;
      case UPDOWN:
         menu->spr = (Sprite *)loadfile("pic\\updown.spr");
         new Button(242, MENUY+menu->y, menu->spr, 0, index);
         new Button(266, MENUY+menu->y, menu->spr, 1, index);
         break;
      case CUSTOM:
         menu->spr = (Sprite *)loadfile(menu->sname);
         n = menu->spr->frames / 2;
         if (menu->x == -1)
            x = MENUX + (MENUXS+BORDER-menu->spr->xs)/2;
         else
            x = MENUX + menu->x;
         y = MENUY + menu->y;
         for (i = 0; i < n; i++) {
            new Button(x, y, menu->spr, i, index);
            y += menu->ys + menu->spr->ys;
         }
         break;
      case END:
         menu->action(M_INIT, 0);
         break;
      }
      if (menu->type == END) break;
      menu++; index++;
   } while (1);

   back->invalidate();
}


/*---------------------------------------------------------
 Function:

 Description:
 Operate menu.
 Check if mouse is pressing a button and execute
 "action function" on a press.
 Return values:
 0 : everything is allright. Go ahead.
 1 : play game
 2 : quit game
---------------------------------------------------------*/
int checkmenu(int mb, int x, int y)
{
   static   Button *last;
   Button   *now;
   static   lindex;
   static   lsindex;
   int      index, subindex;
   int      ret;

// Set up.
   if (mb == -1) {
      last = 0;
      lindex = -1;
      ret = 1;
      return -1;
   }

   ret = 0;

   if (mb) {
      now = checkbuttons(x, y, index, subindex);
   } else {
      now = 0;
      index = -1;
   }
   if (last) last->up();
   if (now) now->down();
   last = now;

   if (lindex != -1) if (nowmenu[lindex].action) {
      if (nowmenu[lindex].fast) ret = nowmenu[lindex].action(lindex, lsindex);
      else if (!mb) {
         ret = nowmenu[lindex].action(lindex, lsindex);
      }
   }

   lindex = index;
   lsindex = subindex;
   return ret;
}


static void initmenu(void)
{
   void  *f;

// Allocate Menu Background backup.
   menuback = new Back(MENUXS, MENUYS);
// Load menu screen.
   f = loadfile("pic\\setup.pcx");
   showpcx(f, 0);
   unloadfile(f);
// Take a snapshot of the plain menu screen.
   menuback->save(MENUX, MENUY);

// Several buttons & fonts.
   blue = new Font("fonts\\blue.fnt");
   tiny = new Font("fonts\\tiny.fnt");
   addsub = (Sprite *)loadfile("pic\\addsub.spr");
   yesno = (Sprite *)loadfile("pic\\yesno.spr");

// Add some text.
   blue->print(210, 7, "SETUP");
#ifdef SHAREWARE
   tiny->vanilla_c(240, 26, "shareware 1.1", 10);
#else
   tiny->vanilla_c(240, 26, "registered 1.1", 10);
#endif

// Copy screen to hardware.
   copyfull(); flippage(); copyfull();

// Load pointer.
   pointer = (Sprite *)loadfile("pic\\pointer.spr");
// Set mouse area.
   setmouselimits(MENUX, MENUY, MENUX+MENUXS-pointer->xs, MENUY+MENUYS-pointer->ys);

// Initialize POINTER animation buffer.
   back = new Back(pointer->xs, pointer->ys);
// Set events to initial values.
   pointer_enabled = 1;
   keys_enabled = 0;
   joy_enabled = 0;


   fadein(getpcxpal());
}

static void shutmenu(void)
{
   fadeout(FADE_ALL);
   palette.unlock();

// release resources.
   unloadfile(yesno);
   unloadfile(addsub);
   delete tiny;
   delete blue;

   delete back;
   unloadfile(pointer);
   delete menuback;

   setscreen(0);
}

static void testlimits(int &x, int &y)
{
   if (x < MENUX) x = MENUX;
   if (x > MENUX+MENUXS-pointer->xs) x = MENUX+MENUXS-pointer->xs;
   if (y < MENUY) y = MENUY;
   if (y > MENUY+MENUYS-pointer->ys) y = MENUY+MENUYS-pointer->ys;
}


/*---------------------------------------------------------
 Function: menu

 Description:
 Build and manage menu.
 mode : Menu mode (MAINMENU or SHOPMENU).
 p    : Build menu for PLAYER p. (Especially important for SHOPMENU)
---------------------------------------------------------*/
int menu(void)
{
   int      dx, dy, db;    // Accumulated cursor coordinates.
   int      joy_x, joy_y, joy_fire; // Joystick values.
   int      joy_last;      // Last fire value for a little MURKS.
   int      x, y, b;       // Mouse coordinates.
   int      tmp;           // Used for stuff.
   static   key_allowed;

// Initialize menu system.
   initmenu();
   nowmenu = 0;            // No menu active at the moment.

   buildmenu(mainmenu);

// Initialize some variables.
   joy_x = joy_y = joy_fire = 0;
   joy_last = 0;

// Initialize "checkmenu".
   checkmenu(-1, 0, 0);

   x = MENUX + MENUXS/2;
   y = MENUY + MENUYS/2;
   do {
// Restore mouse pointer background.
      back->restore();
      b = 0;
      if (mouse) getmousexy(b, x, y);
      if (joypad) {
         getjoypad(joy_x, joy_y, joy_fire);
         x += joy_x*4; y += joy_y*4; b += joy_fire;
      }
      getkeyboard(dx, dy, db); x += dx*4; y += dy*4; b += db;
      testlimits(x, y);
      if (mouse) setmousexy(x, y);
// Process events.
      if (!keys) key_allowed = 1;
      if (!keys_enabled) key_released = 0;
// Send KEY PRESSED message to menu.
      if (!keys && key_released && key_released!=KEY_ESC) {
         if (nowmenu[0].action) nowmenu[0].action(M_KEY, key_released);
         key_released = 0;
      }
// Send joypad messages.
      if (joy_enabled && joy_last && !joy_fire) {
         if (nowmenu[0].action) nowmenu[0].action(M_JOYPAD, 0);
      }
      joy_last = joy_fire;
// Send mouse messages.
      if (pointer_enabled) {
         if (tmp = checkmenu(b, x, y)) break;
      }
// Build backoff menu if ESC has been pressed.
      if (key_allowed && key[KEY_ESC] && backoff) {
         buildmenu(backoff);
         key_allowed = 0;
      }
      back->save(x, y);
      if (pointer_enabled) x_drawsprite(pointer, x, y, 0);

// Copy stuff to Hardware Screen.
      while (timer_ticks < 1);
      copyregion(MENUX, MENUY, MENUXS, MENUYS);

// Wait for retrace.
      while (timer_ticks < 2); timer_ticks = 0;
// Flip (aber ned z'knapp).
      flippage();
   } while (1);

   destroymenu();

   shutsound();
// Shut down menu system.
   shutmenu();
   return tmp;
}


// Custom stuff.

#pragma off (unreferenced)

struct Device {
   char  *card;
   char  *env;
};

static Device  device[] = {
   { "reserved", "reserved" },
   { "No Sound", "NOSND" },
   { "Ensonic SoundScape", "SNDSCAPE" },
   { "SoundBlaster", "BLASTER" },
   { "Gravis Ultrasound", "ULTRASND" },
   { 0, 0 }
};

static char    *jukebox[] = {
   "mods\\menu.uni",
   "level1\\level1.uni",
   "level2\\level2.uni",
#ifndef SHAREWARE
   "level3\\level3.uni",
   "level4\\level4.uni",
   "level5\\level5.uni",
   "level6\\level6.uni",
   "level7\\level7.uni",
   "level8\\level8.uni",
   "level9\\level9.uni",
   "mods\\edi.uni",
#endif
   0
};
static int  juke;
static int  nocard;


static void juke_a_bit(char *mod)
{
   if (nocard) return;

   fadeout(FADE_MUSIC);
   s_unloadmod();
   s_loadmod(mod);
   s_startmod();
}


static void b_sound(int n)
{
   int   x, y;
   char  text[40];

   switch (n) {
   case 0:
// Init Sound system.
      shutsound();
      err_disable();
      initsound(settings.sound_device, 44100);
      err_enable();
      nocard = _errors;
      if (!nocard) {
         s_setvoices(1);
         s_setwavvol(0);
         s_setmodvol(settings.mod_vol);
      }
      addtext1(mainmenu[n], "Sound Driver: ", device[settings.sound_device].card);
      break;

   case 1:
      if (!nocard) s_setmodvol(settings.mod_vol);
      if (settings.mod_vol) {
         itoa(settings.mod_vol, text, 10); strcat(text, "/64");
      } else {
         strcpy(text, "off");
      }
      addtext1(mainmenu[n], "Music Volume: ", text);
      break;

   case 2:
      juke_a_bit(jukebox[juke]);
      addtext1(mainmenu[n], "Jukebox: ", s_modname());
      break;

   case 3:
      mainmenu[n].back->restore();
      mainmenu[n+1].back->restore();
      if (nocard) {
         char *t1 = device[settings.sound_device].card;
         char *t2 = device[settings.sound_device].env;
         char *t3 = " doesn't respond.";
         char *t4 = "Check your ";
         char *t5 = " env. variable.";

         y = MENUY + mainmenu[n].y;
         x = MENUX + MENUXS/2;
         x -= (tiny->textlen(t1)+tiny->textlen(t3))/2;
         tiny->vanilla(x, y, t1, 12);
         x += tiny->textlen(t1);
         tiny->vanilla(x, y, t3, 15);
         x += tiny->textlen(t3);

         y = MENUY + mainmenu[n+1].y;
         x = MENUX + MENUXS/2;
         x -= (tiny->textlen(t2)+tiny->textlen(t4)+tiny->textlen(t5))/2;
         tiny->vanilla(x, y, t4, 15);
         x += tiny->textlen(t4);
         tiny->vanilla(x, y, t2, 12);
         x += tiny->textlen(t2);
         tiny->vanilla(x, y, t5, 15);
         x += tiny->textlen(t5);
      } else {
         char *t1 = "Your sound device works perfectly.";

         y = MENUY + mainmenu[n].y;
         x = MENUX + MENUXS/2;
         tiny->vanilla_c(x, y, t1, 15);
      }
   }
}

static int a_main(int index, int subindex)
{

// Initialize menu.
   if (index == M_INIT) {
      backoff = 0;
      err_disable();
      initsound(settings.sound_device, 44100); reg_exit(shutsound);
      err_enable();
      nocard = _errors;
      juke = 0;
      b_sound(0);
      b_sound(1);
      b_sound(2);
      b_sound(3);
      return 0;
   }

   switch (index) {
   case 0:     // Sound Device.
      settings.sound_device++;
      if (device[settings.sound_device].card == 0) settings.sound_device = 1;
      b_sound(index);
      b_sound(3);
      b_sound(2);
      break;

   case 1:     // MOD volume.
      if ((subindex == 0) && (settings.mod_vol < 64)) settings.mod_vol++;
      if ((subindex == 1) && (settings.mod_vol > 0)) settings.mod_vol--;
      b_sound(index);
      break;

   case 2:     // jukebox.
      juke++;
      if (jukebox[juke] == 0) juke = 0;
      b_sound(2);
      break;

   case 5:     // quit.
      return M_QUIT;
   }

   return 0;
}



